#include <string.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#include <psp2/kernel/clib.h>

#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <SDL2/SDL_mixer.h>
#include <SDL2/SDL_ttf.h>
#include <smpeg2/smpeg.h>
#include <pthread.h>

#include "dynlib.h"
#include "so_util.h"
#include "patch.h"
#include "jni.h"
#include "utils.h"
#include "pthread_patch.h"

extern void *__cxa_finalize;
extern void *__cxa_atexit;
extern void *__stack_chk_fail;
extern void *__aeabi_atexit;
extern void *__sF;
extern void *__assert2;
extern void *__gnu_Unwind_Find_exidx;

static int __stack_chk_guard_fake = 0x42424242;
static FILE __sF_fake[0x100][3];

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offs)
{
	return malloc(length);
}

int munmap(void *addr, size_t length)
{
	free(addr);
	return 0;
}

void __assert2_fake(const char *file, int line, const char *func, const char *expr)
{
	debugPrintf("assertion failed:\n%s:%d (%s): %s\n", file, line, func, expr);
}

DynLibFunction dynlib_functions[] = {
	// __
	{"__cxa_finalize", (uintptr_t)&__cxa_finalize},
	{"__cxa_atexit", (uintptr_t)&__cxa_atexit},
	{"__stack_chk_fail", (uintptr_t)&__stack_chk_fail},
	{"__stack_chk_guard", (uintptr_t)&__stack_chk_guard_fake},
	{"__aeabi_atexit", (uintptr_t)&__aeabi_atexit},
	{"__sF", (uintptr_t)&__sF_fake},
	{"__assert2", (uintptr_t)&__assert2_fake},
	{"__gnu_Unwind_Find_exidx", (uintptr_t)&ret0},

	{"__android_log_print", (uintptr_t)&__android_log_print},
	{"__android_log_vprint", (uintptr_t)&__android_log_vprint},

	// Android_JNI
	{"Android_JNI_GetEnv", (uintptr_t)&Android_JNI_GetEnv},
	{"Android_JNI_FileOpen", (uintptr_t)&Android_JNI_FileOpen},
	{"Android_JNI_FileRead", (uintptr_t)&Android_JNI_FileRead},
	{"Android_JNI_FileSeek", (uintptr_t)&Android_JNI_FileSeek},
	{"Android_JNI_FileClose", (uintptr_t)&Android_JNI_FileClose},
	{"Android_JNI_FileSize", (uintptr_t)&Android_JNI_FileSize},

	// C
	{"time", (uintptr_t)&time},
	{"localtime", (uintptr_t)&localtime},
	{"sprintf", (uintptr_t)&sprintf},
	{"strcpy", (uintptr_t)&strcpy},
	{"memset", (uintptr_t)&memset},
	{"memcmp", (uintptr_t)&memcmp},
	{"memcpy", (uintptr_t)&memcpy},
	{"memmove", (uintptr_t)&memmove},
	{"cos", (uintptr_t)&cos},
	{"sin", (uintptr_t)&sin},
	{"floor", (uintptr_t)&floor},
	{"sqrt", (uintptr_t)&sqrt},
	{"printf", (uintptr_t)&printf},
	{"exit", (uintptr_t)&exit},
	{"malloc", (uintptr_t)&malloc},
	{"realloc", (uintptr_t)&realloc},
	{"free", (uintptr_t)&free},
	{"fclose", (uintptr_t)&fclose},
	{"truncate", (uintptr_t)&truncate},
	{"strlen", (uintptr_t)&strlen},
	{"open", (uintptr_t)&open},
	{"lseek", (uintptr_t)&lseek},
	{"mmap", (uintptr_t)&mmap},
	{"calloc", (uintptr_t)&calloc},
	{"puts", (uintptr_t)&puts},
	{"abort", (uintptr_t)&abort},
	{"lrand48", (uintptr_t)&lrand48},
	{"tan", (uintptr_t)&tan},
	{"srand48", (uintptr_t)&srand48},
	{"fprintf", (uintptr_t)&fprintf},
	{"putchar", (uintptr_t)&putchar},
	{"strcat", (uintptr_t)&strcat},
	{"atoi", (uintptr_t)&atoi},
	{"strcmp", (uintptr_t)&strcmp},
	{"sqrtf", (uintptr_t)&sqrtf},
	{"difftime", (uintptr_t)&difftime},
	{"fopen", (uintptr_t)&fopen},
	{"strstr", (uintptr_t)&strstr},
	{"close", (uintptr_t)&close},
	{"fread", (uintptr_t)&fread},
	{"fwrite", (uintptr_t)&fwrite},
	{"ftell", (uintptr_t)&ftell},
	{"fseek", (uintptr_t)&fseek},
	{"munmap", (uintptr_t)&munmap},
	{"dlopen", (uintptr_t)&dlopen},
	{"dlsym", (uintptr_t)&dlsym},
	{"dlclose", (uintptr_t)&dlclose},
	{"raise", (uintptr_t)&raise},

	// SDL2
	{"SDL_Android_Init", (uintptr_t)&SDL_Android_Init_soloader},
	{"SDL_SetMainReady", (uintptr_t)&SDL_SetMainReady},
	{"SDL_strdup", (uintptr_t)&SDL_strdup},
	{"SDL_free", (uintptr_t)&SDL_free},
	{"SDL_GetTicks", (uintptr_t)&SDL_GetTicks},
	{"SDL_LogError", (uintptr_t)&SDL_LogError},
	{"SDL_CreateRGBSurfaceFrom", (uintptr_t)&SDL_CreateRGBSurfaceFrom},
	{"SDL_UpperBlit", (uintptr_t)&SDL_UpperBlit},
	{"SDL_SetColorKey", (uintptr_t)&SDL_SetColorKey},
	{"SDL_FillRect", (uintptr_t)&SDL_FillRect},
	{"SDL_GetWindowFlags", (uintptr_t)&SDL_GetWindowFlags},
	{"SDL_Log", (uintptr_t)&SDL_Log},
	{"SDL_LockSurface", (uintptr_t)&SDL_LockSurface},
	{"SDL_UnlockSurface", (uintptr_t)&SDL_UnlockSurface},
	{"SDL_GetError", (uintptr_t)&SDL_GetError},
	{"SDL_malloc", (uintptr_t)&SDL_malloc},
	{"SDL_FreeSurface", (uintptr_t)&SDL_FreeSurface},
	{"SDL_IntersectRect", (uintptr_t)&SDL_IntersectRect},
	{"SDL_MapRGB", (uintptr_t)&SDL_MapRGB},
	{"SDL_CreateMutex", (uintptr_t)&SDL_CreateMutex},
	{"SDL_CreateTexture", (uintptr_t)&SDL_CreateTexture},
	{"SDL_DestroyMutex", (uintptr_t)&SDL_DestroyMutex},
	{"SDL_DestroyTexture", (uintptr_t)&SDL_DestroyTexture},
	{"SDL_LockMutex", (uintptr_t)&SDL_LockMutex},
	{"SDL_UpdateTexture", (uintptr_t)&SDL_UpdateTexture},
	{"SDL_UnlockMutex", (uintptr_t)&SDL_UnlockMutex},
	{"SDL_RenderCopy", (uintptr_t)&SDL_RenderCopy},
	{"SDL_RenderPresent", (uintptr_t)&SDL_RenderPresent_soloader},
	{"SDL_Delay", (uintptr_t)&SDL_Delay},
	{"SDL_RWFromMem", (uintptr_t)&SDL_RWFromMem},
	{"SDL_GetKeyboardState", (uintptr_t)&SDL_GetKeyboardState},
	{"SDL_memcpy", (uintptr_t)&SDL_memcpy},
	{"SDL_SetError", (uintptr_t)&SDL_SetError},
	{"SDL_FreeRW", (uintptr_t)&SDL_FreeRW},
	{"SDL_DestroyRenderer", (uintptr_t)&SDL_DestroyRenderer},
	{"SDL_AudioQuit", (uintptr_t)&SDL_AudioQuit},
	{"SDL_RWFromFile", (uintptr_t)&SDL_RWFromFile},
	{"SDL_LoadBMP_RW", (uintptr_t)&SDL_LoadBMP_RW},
	{"SDL_GetKeyName", (uintptr_t)&SDL_GetKeyName},
	{"SDL_GetWindowFromID", (uintptr_t)&SDL_GetWindowFromID},
	{"SDL_DestroyWindow", (uintptr_t)&SDL_DestroyWindow},
	{"SDL_SetWindowFullscreen", (uintptr_t)&SDL_SetWindowFullscreen},
	{"SDL_AllocRW", (uintptr_t)&SDL_AllocRW},
	{"SDL_CreateRGBSurface", (uintptr_t)&SDL_CreateRGBSurface},
	{"SDL_SetRenderDrawColor", (uintptr_t)&SDL_SetRenderDrawColor},
	{"SDL_RenderClear", (uintptr_t)&SDL_RenderClear},
	{"SDL_CreateTextureFromSurface", (uintptr_t)&SDL_CreateTextureFromSurface},
	{"SDL_Error", (uintptr_t)&SDL_Error},
	{"SDL_Quit", (uintptr_t)&SDL_Quit},
	{"SDL_VideoInit", (uintptr_t)&SDL_VideoInit_soloader},
	{"SDL_GL_SetAttribute", (uintptr_t)&SDL_GL_SetAttribute},
	{"SDL_memset", (uintptr_t)&SDL_memset},
	{"SDL_CreateRenderer", (uintptr_t)&SDL_CreateRenderer_soloader},
	{"SDL_SetHint", (uintptr_t)&SDL_SetHint},
	{"SDL_RenderSetLogicalSize", (uintptr_t)&SDL_RenderSetLogicalSize},
	{"SDL_PixelFormatEnumToMasks", (uintptr_t)&SDL_PixelFormatEnumToMasks},
	{"SDL_AudioInit", (uintptr_t)&SDL_AudioInit_soloader},
	{"SDL_GetNumVideoDrivers", (uintptr_t)&SDL_GetNumVideoDrivers},
	{"SDL_snprintf", (uintptr_t)&SDL_snprintf},
	{"SDL_CreateWindow", (uintptr_t)&SDL_CreateWindow_soloader},
	{"SDL_SetWindowMinimumSize", (uintptr_t)&SDL_SetWindowMinimumSize},
	{"SDL_SetWindowMaximumSize", (uintptr_t)&SDL_SetWindowMaximumSize},
	{"SDL_GetWindowSize", (uintptr_t)&SDL_GetWindowSize},
	{"SDL_SetWindowDisplayMode", (uintptr_t)&SDL_SetWindowDisplayMode_soloader},
	{"SDL_ShowWindow", (uintptr_t)&SDL_ShowWindow_soloader},
	{"SDL_GetNumRenderDrivers", (uintptr_t)&SDL_GetNumRenderDrivers},
	{"SDL_GetRenderDriverInfo", (uintptr_t)&SDL_GetRenderDriverInfo},
	{"SDL_strcasecmp", (uintptr_t)&SDL_strcasecmp},
	{"SDL_strlcpy", (uintptr_t)&SDL_strlcpy},
	{"SDL_GetRendererInfo", (uintptr_t)&SDL_GetRendererInfo},
	{"SDL_RenderSetScale", (uintptr_t)&SDL_RenderSetScale},
	{"SDL_GetCurrentVideoDriver", (uintptr_t)&SDL_GetCurrentVideoDriver},
	{"SDL_GetNumVideoDisplays", (uintptr_t)&SDL_GetNumVideoDisplays},
	{"SDL_GetDisplayName", (uintptr_t)&SDL_GetDisplayName},
	{"SDL_GetDisplayBounds", (uintptr_t)&SDL_GetDisplayBounds},
	{"SDL_GetDesktopDisplayMode", (uintptr_t)&SDL_GetDesktopDisplayMode},
	{"SDL_GetPixelFormatName", (uintptr_t)&SDL_GetPixelFormatName},
	{"SDL_GetNumDisplayModes", (uintptr_t)&SDL_GetNumDisplayModes},
	{"SDL_GetDisplayMode", (uintptr_t)&SDL_GetDisplayMode},
	{"SDL_GetNumAudioDrivers", (uintptr_t)&SDL_GetNumAudioDrivers},
	{"SDL_GetCurrentAudioDriver", (uintptr_t)&SDL_GetCurrentAudioDriver},
	{"SDL_GetVideoDriver", (uintptr_t)&SDL_GetVideoDriver},
	{"SDL_GetAudioDriver", (uintptr_t)&SDL_GetAudioDriver},
	{"SDL_PollEvent", (uintptr_t)&SDL_PollEvent},
	{"SDL_GetNumTouchFingers", (uintptr_t)&SDL_GetNumTouchFingers},
	{"SDL_GetTouchFinger", (uintptr_t)&SDL_GetTouchFinger},

	// SDL2_ttf
	{"TTF_CloseFont", (uintptr_t)&TTF_CloseFont},
	{"TTF_OpenFontRW", (uintptr_t)&TTF_OpenFontRW},
	{"TTF_RenderUTF8_Solid", (uintptr_t)&TTF_RenderUTF8_Solid},
	{"TTF_RenderUTF8_Blended", (uintptr_t)&TTF_RenderUTF8_Blended},
	{"TTF_RenderUTF8_Shaded", (uintptr_t)&TTF_RenderUTF8_Shaded},
	{"TTF_Quit", (uintptr_t)&TTF_Quit},
	{"TTF_Init", (uintptr_t)&TTF_Init},

	// SDL2_mixer
	{"Mix_QuerySpec", (uintptr_t)&Mix_QuerySpec},
	{"Mix_HookMusic", (uintptr_t)&Mix_HookMusic},
	{"Mix_HookMusicFinished", (uintptr_t)&Mix_HookMusicFinished},
	{"Mix_HaltMusic", (uintptr_t)&Mix_HaltMusic},
	{"Mix_FreeMusic", (uintptr_t)&Mix_FreeMusic},
	{"Mix_LoadMUS_RW", (uintptr_t)&Mix_LoadMUS_RW},
	{"Mix_VolumeMusic", (uintptr_t)&Mix_VolumeMusic},
	{"Mix_PauseMusic", (uintptr_t)&Mix_PauseMusic},
	{"Mix_Pause", (uintptr_t)&Mix_Pause},
	{"Mix_ResumeMusic", (uintptr_t)&Mix_ResumeMusic},
	{"Mix_Resume", (uintptr_t)&Mix_Resume},
	{"Mix_PlayMusic", (uintptr_t)&Mix_PlayMusic},
	{"Mix_LoadMUS", (uintptr_t)&Mix_LoadMUS},
	{"Mix_PlayChannelTimed", (uintptr_t)&Mix_PlayChannelTimed},
	{"Mix_Volume", (uintptr_t)&Mix_Volume},
	{"Mix_SetPanning", (uintptr_t)&Mix_SetPanning},
	{"Mix_HaltChannel", (uintptr_t)&Mix_HaltChannel},
	{"Mix_FreeChunk", (uintptr_t)&Mix_FreeChunk},
	{"Mix_AllocateChannels", (uintptr_t)&Mix_AllocateChannels},
	{"Mix_ChannelFinished", (uintptr_t)&Mix_ChannelFinished},
	{"Mix_LoadWAV_RW", (uintptr_t)&Mix_LoadWAV_RW},
	{"Mix_Quit", (uintptr_t)&Mix_Quit},
	{"Mix_Init", (uintptr_t)&Mix_Init},
	{"Mix_OpenAudio", (uintptr_t)&Mix_OpenAudio},

	// SDL2_image
	{"IMG_Load_RW", (uintptr_t)&IMG_Load_RW},

	// smpeg2
	{"SMPEG_new_rwops", (uintptr_t)&SMPEG_new_rwops},
	{"SMPEG_enableaudio", (uintptr_t)&SMPEG_enableaudio},
	{"SMPEG_enablevideo", (uintptr_t)&SMPEG_enablevideo},
	{"SMPEG_setvolume", (uintptr_t)&SMPEG_setvolume},
	{"SMPEG_actualSpec", (uintptr_t)&SMPEG_actualSpec},
	{"SMPEG_setdisplay", (uintptr_t)&SMPEG_setdisplay},
	{"SMPEG_playAudioSDL", (uintptr_t)&SMPEG_playAudioSDL},
	{"SMPEG_stop", (uintptr_t)&SMPEG_stop},
	{"SMPEG_delete", (uintptr_t)&SMPEG_delete},
	{"SMPEG_status", (uintptr_t)&SMPEG_status},
	{"SMPEG_play", (uintptr_t)&SMPEG_play},
	{"SMPEG_pause", (uintptr_t)&SMPEG_pause},

	// pthread
	{"pthread_mutex_destroy", (uintptr_t)&pthread_mutex_destroy_fake},
	{"pthread_mutex_lock", (uintptr_t)&pthread_mutex_lock_fake},
	{"pthread_mutex_unlock", (uintptr_t)&pthread_mutex_unlock_fake},
	{"pthread_mutex_init", (uintptr_t)&pthread_mutex_init_fake},
	{"pthread_setspecific", (uintptr_t)&pthread_setspecific},
	{"pthread_getspecific", (uintptr_t)&pthread_getspecific},
	{"pthread_key_create", (uintptr_t)&pthread_key_create},
	{"pthread_key_delete", (uintptr_t)&pthread_key_delete},
};
#define N_DYNLIB_FUNCTIONS sizeof(dynlib_functions) / sizeof(DynLibFunction)

int getNumberOfDynlibFunctions()
{
	return N_DYNLIB_FUNCTIONS;
}